一、AOP
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑
各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
1.1 主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。
1.2 主要目标
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变
这些行为的时候不影响业务逻辑的代码。
1.3 适用对象
比较大型的项目,而且迭代较快,使用OOP太消耗内力。
有日志、性能、安全、异常处理等横切关注点需求。
1.4 AOP与OOP的关系
OOP(面向对象编程)针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。但是也有它的缺点,最明显的就是关注点聚焦时,面向对象无法简单的解决这个问题,一个关注点是面向所有而不是单一的类,不受类的边界的约束,因此OOP无法将关注点聚焦来解决,只能分散到各个类中。
AOP(面向切面编程)则是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。这两种设计思想在目标上有着本质的差异。
AOP并不是与OOP对立的,而是为了弥补OOP的不足。OOP解决了竖向的问题,AOP则解决横向的问题。因为有了AOP我们的调试和监控就变得简单清晰。它们之间的关系如下图所示:
1.4.1 对比一——单一横切关注点
1.4.2 对比二——多横切关注点
结论:
二、Android中使用@AspectJ
AspectJ 意思就是Java的Aspect,Java的AOP。它其实不是一个新的语言,它的核心是ajc(编译器)\weaver(织入器)。
- ajc编译器:基于Java编译器之上的,它是用来编译.aj文件,aspectj在Java编译器的基础上增加了一些它自己的关键字和方法。因此,ajc也可以编译Java代码。
- weaver织入器:为了在java编译器上使用AspectJ而不依赖于Ajc编译器,aspectJ 5出现了@AspectJ,使用注释的方式编写AspectJ代码,可以在任何Java编译器上使用。
由于AndroidStudio默认是没有ajc编译器的,所以在Android中使用@AspectJ来编写(包括SpringAOP也是如此)。它在代码的编译期间扫描目标程序,根据切点(PointCut)匹配,将开发者编写的Aspect程序编织(Weave)到目标程序的.class文件中,对目标程序作了重构(重构单位是JoinPoint),目的就是建立目标程序与Aspect程序的连接(获得执行的对象、方法、参数等上下文信息),从而达到AOP的目的。
2.1 Gradle 配置示例
要引入AspectJ到Android工程中,最重要的就是两个包:
//在buildscript中添加该编织器,gradle构建时就会对class文件进行编织
classpath 'org.aspectj:aspectjweaver:1.8.9'
//在dependencies中添加该依赖,提供@AspectJ语法
compile 'org.aspectj:aspectjrt:1.8.9'
此外还有一个工具包,用于Gradle构建时进行打日志等操作:
//在buildscript中添加该工具包,在构建工程的时候执行一些任务:打日志等
classpath 'org.aspectj:aspectjtools:1.8.9'import com.android.build.gradle.LibraryPlugin
import org.aspectj.bridge.IMessage
import org.aspectj.bridge.MessageHandler
import org.aspectj.tools.ajc.Main
//打印gradle日志
android.libraryVariants.all { variant >
LibraryPlugin plugin = project.plugins.getPlugin(LibraryPlugin)
JavaCompile javaCompile = variant.javaCompile
javaCompile.doLast {
String[] args = ["showWeaveInfo",
"1.5",
"inpath", javaCompile.destinationDir.toString(),
"aspectpath", javaCompile.classpath.asPath,
"d", javaCompile.destinationDir.toString(),
"classpath", javaCompile.classpath.asPath,
"bootclasspath",
project.android.bootClasspath.join(
File.pathSeparator)]
MessageHandler handler = new MessageHandler(true);
new Main().run(args, handler)
def log = project.logger
for (IMessage message : handler.getMessages(null, true)) {
switch (message.getKind()) {
case IMessage.ABORT:
case IMessage.ERROR:
case IMessage.FAIL:
log.error message.message, message.thrown
break;
case IMessage.WARNING:
case IMessage.INFO:
log.info message.message, message.thrown
break;
case IMessage.DEBUG:
log.debug message.message, message.thrown
break;
}
}
}
}
附:美团RoboAspectJ
buildscript {
repositories {
mavenLocal()
}
dependencies {
classpath 'com.meituan.gradle:roboaspectj:0.9.2'
classpath 'jaop.gradle.plugin:gradleplugin:1.0.2'
}
// Exclude the version that the android plugin depends on.
configurations.classpath.exclude group: 'com.android.tools.external.lombok'
}
配置参数
// AspectJ
aspectj {
disableWhenDebug true
javartNeeded true
// 排除不需要AOP扫描的包
exclude group: 'xxxx', module: 'xxxx'
compileOptions {
defaultJavaVersion = JavaVersion.VERSION_1_7
}
}
2.2 基本概念
2.2.1 切面——Aspect
实现了crosscutting功能,是针对切面的模块。最常见的是logging模块、方法执行耗时模块,这样,程序按功能被分为好几层,如果按传统的继承的话,商业模型继承日志模块的话需要插入修改的地方太多,而通过创建一个切面就可以使用AOP来实现相同的功能了,我们可以针对不同的需求做出不同的切面。
2.2.2 连接点——JoinPoint
连接点是切面插入应用程序的地方,该点能被方法调用,而且也会被抛出意外。连接点是应用程序提供给切面插入的地方,可以添加新的方法。比如:我们的切点可以认为是findInfo(String)方法。
AspectJ将面向对象的程序执行流程看成是JoinPoint的执行链,每一个JoinPoint是一个单独的闭包,在执行的时候将上下文环境赋予闭包执行方法体逻辑。
下面列表上的是被AspectJ认为是joinpoint的:
2.2.3 切点——PointCut
切点的声明决定需要切割的JoinPoint的集合,就结果上来说,它是JoinPoint的一个实际子集合。
pointcut可以控制你把哪些advice应用于jointpoint上去,通常通过正则表达式来进行匹配应用,决定了那个jointpoint会获得通知。分为call、execution、target、this、within等关键字,含义下面会附图。
1.直接针对JoinPoint的选择
pointcuts中最常用的选择条件和Joinpoint的类型密切相关,比如图5:
2.间接针对JPoint的选择
除了根据前面提到的Signature信息来匹配JPoint外,AspectJ还提供其他一些选择方法来选择JPoint。比如某个类中的所有JPoint,每一个函数执行流程中所包含的JPoint。
特别强调,不论什么选择方法,最终都是为了找到目标的JPoint。
表2列出了一些常用的非JPoint选择方法:
3.匹配规则
(1)类型匹配语法
首先让我们来了解下AspectJ类型匹配的通配符:
*:匹配任何数量字符;
..:匹配任何数量字符的重复,如在类型模式中匹配任何数量子包;而在方法参数模式中匹配任何数量参数。
+:匹配指定类型的子类型;仅能作为后缀放在类型模式后边。
AspectJ使用 且(&&)、或(||)、非(!)来组合切入点表达式。
(2)匹配模式
call(<注解?> <修饰符?> <返回值类型> <类型声明?>.<方法名>(参数列表) <异常列表>?)
- 精确匹配
//表示匹配 com.davidkuper.MainActivity类中所有被@Describe注解的public void方法。
@Pointcut("call(@Describe public void com.davidkuper.MainActivity.init(Context))")
public void pointCut(){}
- 单一模糊匹配
//表示匹配 com.davidkuper.MainActivity类中所有被@Describe注解的public void方法。
@Pointcut("call(@Describe public void com.davidkuper.MainActivity.*(..)) ")
public <